﻿/* Copyright © 2012 Gargol, Gargol at gbot dot org
   Based on CoreDC Copyright 2007 © Johan Pettersson, Fredrik Johansson, Michael Starberg, Christian Epstein.
 
This file is part of ADCCore.

ADCCore is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.

ADCCore is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with ADCCore.  If not, see <http://www.gnu.org/licenses/>. */
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using System.Net;

namespace ADCCore.Implementation
{
    using Classes;
    internal class ProtocolHandler
    {
        readonly DcClient client;

        public ProtocolHandler(DcClient client)
        {
            this.client = client;
        }

        /// <summary>
        /// Handles protocol commands and trigger the proper events based
        /// on the type of command it recieves.
        /// </summary>
        /// <param name="message">The message.</param>
        public void ProcessCommand(string message)
        {
            client.HubInfo.LastMessage = message;
            client.DoMessageReceived(message);

            string type = "";
            string command = "";
            string parameter = "";

            try
            {
                // split message into command and parameters
                type = message.Substring(0, 1);
                command = message.Substring(1, 3);
                parameter = message.Substring(message.IndexOf(" ") + 1);
            }
            catch
            {
                // Command has no parameters. ($GetPass for example)
                command = message;
                parameter = "";
            }
            if (System.Threading.Thread.CurrentThread.Name == null)
                System.Threading.Thread.CurrentThread.Name = "ProcessCommand" + command;
            String[] param;
            switch (command)
            {
                /* 
                 STA <code> <param1> <param2> … <paramN> <description>
                 * Types: C, D, I
                 * States: All
                     
                 SUP <feature1> <feature2> … <featureN>
                 * Types: C, H, I
                 * States: PROTOCOL 
                                          
                    
                 * PAS <password>
                 * Types: H
                 * States: VERIFY
                 */
                case "INF":
                    // Client information string, sent for all users on login, and when new users joins
                    // INF <CID> <field1> <field2> … <fieldN>
                    //* Types: B, C, I
                    //* States: IDENTIFY, NORMAL
                    switch (type)
                    {
                        case "I":
                            DoIINF(parameter);
                        break;
                        default:
                            param = parameter.Split(" ".ToCharArray(), 2);
                            DoINF(param[0], param[1]);
                        break;
                    }
                    /*if (type == "I")
                    {
                        //param = parameter.Split(" ".ToCharArray(), 2);
                        //DoHubInfo(param[0], param[1]);
                        DoIINF(parameter);
                    }
                    else
                    {
                        param = parameter.Split(" ".ToCharArray(), 2);
                        DoINF(param[0], param[1]);
                    }*/
                    break;
                case "MSG":
                    // Private message
                    //MSG <my-CID> <text>
                    //* Types: A, B, D, I, P
                    if (type == "B")
                        DoMSG(parameter);
                    else
                        DoEMSG(parameter);
                    break;
                case "LIV":
                    DoAliveMessage(parameter);
                    break;
                case "STA":
                    DoStatusMessage(parameter);
                    //DoMainchatMessage();
                    break;
                case "SUP":
                    DoSupports(parameter);
                    break;
                case "SCH":
                    // Search sent from other clients, most common protocolmessage
                    // SCH <my-CID> <field1> <field2> … <fieldN>
                    // * Types: P,U,D,(B)
                    DoSearch(parameter);
                    break;
                case "QUI":
                    // Specified user has exited the hub
                    // QUI <CID> <reason> <param1>…<paramN> <message>
                    if (parameter.Contains(" "))
                        DoQUI(parameter.Split(" ".ToCharArray()));
                    else
                        DoQUI(parameter);
                    break;
                case "RES":
                    // Searchresult sent back if passive
                    //RES <my-CID> <field1> <field2> … <fieldN>
                    //Types: D, U
                    DoSCH(parameter);
                    break;
                case "CTM":
                    // Another client wants us to initiate client to client connection (active users)
                    //* CTM <my-CID> <token> <proto> <port>
                    //* Types: D
                    DoCTM(parameter);
                    break;
                case "RCM":
                    // Another client wants us to ask them to initiate client to client connection (passive users)
                    //* RCM <my-CID> <token> <proto>
                    //* Types: D
                    DoRCM(parameter);
                    break;
                case "SID":
                    DoSID(parameter);
                    break;
                case "GPA":
                    // A password is required, nick is probably registered
                    // GPA <data>
                    //
                    DoGetPass(parameter);
                    break;
                case "CMD":
                    DoUserCommand(parameter);
                    break;
                /*
                // list of extra (non-original-adc) features supported by hub (& clients)
                */
                default:
                    // weird mainchat messages?
                    DoMSG(message);
                    break;
            }
        }

        private void DoAliveMessage(string parameter)
        {
            // ILIV TOtoken
            var token = parameter.Substring(2, parameter.Length - 2);
            client.SendAlive(token);
        }

        /// <summary>
        /// Status information sent from Hub
        /// </summary>
        /// <param name="parameter">Status code and data</param>
        private void DoStatusMessage(string parameter)
        {
            String[] status = parameter.Split(" ".ToCharArray());
            int SCode = Convert.ToInt32(status[0]);
            if (SCode < 200)
            {
                //1-99 = Success (used for confirming commands)
                //100-199 = Recoverable (error but no disconnect) 
                client.DoStatusMessageRecived(SCode, status[1]);
            }
            else if (SCode == 200)
            {
                
            }
            else if (SCode < 300)
            {
                //Fatal (disconnect)
                if (SCode == 223)
                    DoBadPass();
                else
                {
                    client.DoStatusMessageRecived(SCode, status[1]);
                    client.Disconnect();
                    return;
                }   
            }
        }

        /// <summary>
        /// Sent from the hub when we enter, and when hubname and/or topic changes
        /// </summary>
        /// <param name="parameter">Contains the IINF data</param>
        private void DoIINF(string parameter)
        {
            foreach (string p in parameter.Split(" ".ToCharArray()))
            {
                switch (p)
                {
                    case "NI":
                        client.HubInfo.Hubname= DcClient.DcDecode(p.Substring(2,p.Length-2));
                        break;
                    case "DE":
                        client.HubInfo.Topic= DcClient.DcDecode(p.Substring(2,p.Length-2));
                        break;
                    case "VE":
                        client.HubInfo.HubVersion= DcClient.DcDecode(p.Substring(2,p.Length-2));
                        break;
                    case "RE":
                        DoReDirect(DcClient.DcDecode(p.Substring(2,p.Length-2)));
                        break;
                }
                    
            }
            client.DoTopic(client.HubInfo.Topic);
        }
        
        private void DoSID(string parameter)
        {
            client.SID = parameter;
            User u = client.NickList.Get(parameter);
            u.CID = client.ClientInfo.CID;
            u.Username = client.ClientInfo.Username;
            u.Description = client.ClientInfo.Description;
            u.IP = client.ClientInfo.ClientIP;
            u.Port = client.ClientInfo.ClientPort;
            u.Tag = client.ClientInfo.Tag;
            client.NickList.Set(u);

            StringBuilder Binf = new StringBuilder();
            Binf.AppendFormat("BINF {0} ID{1} PD{2} ", client.SID, u.CID, client.ClientInfo.PID);
            Binf.AppendFormat("I4{0} U4{1} SS{2} SF{3} ", u.IP, u.Port, u.ShareSize,u.SharedFiles);
            Binf.AppendFormat("VE{0} SL{1} ", u.Tag.Replace(" ", "\\s"), u.OpenSlots+1);
            Binf.AppendFormat("NI{0} DE{1} ", u.Username.Replace(" ", "\\s"), u.Description.Replace(" ", "\\s"));
            Binf.AppendFormat("HN{0} HR{1} HO{2}\n", u.HubsNormal, u.HubsReg, u.HubsOP);
            client.SendRawMessage(Binf.ToString());
            //client.DoConnected();
        }

        /// <summary>
        /// A password is required. Either our username is registered, or hub requires global password.
        /// </summary>
        void DoGetPass(String passkey)
        {
            ADCCore.TTH.Tiger tiger = new ADCCore.TTH.Tiger();
            byte[] data = System.Text.Encoding.UTF8.GetBytes(client.ClientInfo.Password);
            System.IO.MemoryStream ms = new System.IO.MemoryStream();
            ms.Write(data, 0, data.Length);
            data = Base32.Decode(passkey);
            ms.Write(data, 0, data.Length);

            data = tiger.ComputeHash(ms.ToArray());

            client.SendRawMessage("HPAS " + Base32.Encode(data) + "\n");
        }


        /// <summary>
        /// The password we sent was incorrect according to the hub. 
        /// </summary>
        void DoBadPass()
        {
            // Note: not all hubs support this.
            client.DoPasswordRejected();
        }

        /// <summary>
        /// Hub supports Extended protocol features.
        /// </summary>
        /// <param name="parameter">Space delimited list of supported EP features.</param>
        void DoSupports(string parameter)
        {
            string[] supports = parameter.Split(" ".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
            List<string> supportlist = new List<string>(supports.Length);
            foreach (String s in supports)
            {
                if (s.StartsWith("AD"))
                    supportlist.Add(s.Substring(2, s.Length - 2));
                else if (s.StartsWith("RM"))
                    supportlist.Remove(s.Substring(2, s.Length - 2));
            }
            client.HubInfo.HubSupports = supportlist;
        }


        /// <summary>
        /// We have been redirected from the hub.
        /// </summary>
        /// <param name="parameter">Redirection hub address</param>
        void DoReDirect(string parameter)
        {
            // REnew\shubb\sadd

            // TODO: Needs cleanup. Extend HubInfo to include IP and Port and use that?
            client.DoRedirected(parameter);

            if (client.ClientInfo.FollowRedirects)
            {
                client.Disconnect();
                client.ClientInfo.HubAdress = parameter;
                client.Connect();
            }
        }


        /// <summary>
        /// Recieved a UserCommand from the hub.
        /// </summary>
        /// <param name="parameter">UserCommand string</param>
        void DoUserCommand(string parameter)
        {
            // TODO: Need to make UserCommand a proper class and split this nicely to fit.
            String[] parms = parameter.Split(" ".ToCharArray());
            UserCommand uc = new UserCommand();
            foreach (String cmd in parms)
            {
                switch (cmd.Substring(0, 2))
                {
                    case "TT":
                        uc.UserPath = DcClient.DcDecode(parms[0]);
                        break;
                    case "CT":
                        break;
                    default:
                        uc.UserCmd = DcClient.DcDecode(parms[0]);
                        break;
                }
            }
            client.DoUserCommand(uc);
        }


        /// <summary>
        /// A user has exited the hub.
        /// </summary>
        /// <param name="parameter">The username of the quitter</param>
        void DoQUI(string parameter)
        {
            User user = client.NickList[parameter];
            if (user != null)
            {
                if (user.IsOperator)
                {
                    client.NickList.RemoveOperator(user.Username);
                }
                client.NickList.Remove(user);
                client.DoParts(user.Clone());
            }
        }
        void DoQUI(string[] par)
        {
            foreach (string s in par)
            {
                switch (s)
                {
                    case "ID":
                        //SID of the initiator of the disconnect (for example the one that issued a kick).
                        break;
                    case "TL":
                        //Time Left until reconnect is allowed, in seconds. -1 = forever. 

                        break;
                    case "MS":
                        //Message
                        DoMSG(DcClient.DcDecode(s.Substring(2, s.Length - 2)));
                        break;
                    case "RD":
                        //Redirect server URL. 
                        DoReDirect(DcClient.DcDecode(s.Substring(2, s.Length - 2)));
                        break;
                    case "DI":
                        //Any client that has this flag in the QUI message should have its transfers terminated by other clients connected to it, as it is unwanted in the system.
                        break;
                }
                client.DoDisconnected(false);
            }
        }

        /// <summary>
        /// A list of users who are operators.
        /// </summary>
        /// <param name="parameter">The list of users</param>
        void DoOpList(User u)
        {
            client.NickList.SetOperators(u.SID);
        }


        /// <summary>
        /// A user (active) wants us to initiate a client-to-client connection to them
        /// </summary>
        /// <param name="parameter">String with connection specific data</param>
        void DoCTM(string parameter)
        {
            bool ssl = false;
            //CONO F6MN ADC/1.0 5766 foobar
            String[] args = parameter.Split(" ".ToCharArray());

            // Find users with matching IP
            if (args[2] == "ADCS/0.10")
                ssl = true;
            User user = client.NickList.Get(args[0]);
            client.DoClientConnectionRecieved(user,args[4],Convert.ToInt32(args[3]),ssl);
        }


        /// <summary>
        /// A user (passive) wants us to ask them to initiate a client-to-client connection to us
        /// </summary>
        /// <param name="parameter">String with connection specific data</param>
        void DoRCM(string parameter)
        {

            // RCM <my-CID> <token> <proto>
            bool ssl = false;
            string[] args = parameter.Split(" ".ToCharArray());
            if (args[2] == "ADCS/0.10")
                ssl = true;
            User user = client.NickList.Get(args[0]).Clone();

            client.DoReverseClientConnectionRecieved(user,args[1],0,ssl);

            if (client.ClientInfo.RespondToRevConnectToMe)
            {
                client.SendConnectToMe(user);
            }
        }



        /// <summary>
        /// Someone (or something) sent a mainchat message
        /// </summary>
        /// <param name="message">The message, might also include the senders nickname</param>
        void DoMSG(string message)
        {
            string[] mess = message.Split(" ".ToCharArray());
            User u = client.NickList.Get(mess[0]);
            if (u.Username != "")
            {
                try
                {
                    bool ownMessage = (u.Username == client.Username);
                    client.DoMainchatMessage(u.Clone(), DcClient.DcDecode(mess[1]), ownMessage);
                }
                catch
                {
                    // Command was malformed
                    client.DoMainchatMessage(new User(), message, false);
                }
            }
            else
            {

            }
        }


        /// <summary>
        /// Someone sent us a private message.
        /// </summary>
        /// <param name="parameter">String with PM specific data</param>
        void DoEMSG(string parameter)
        {
            string[] mess = parameter.Split(" ".ToCharArray());
            User user = client.NickList.Get(mess[0]);
            if (user.SID == client.SID)
                return;
            user.ReplyTo = user.SID;

            // Get message
            string text = DcClient.DcDecode(mess[2]) ?? String.Empty;

            client.DoPrivateMessageRecieved(user, text);
        }


        /// <summary>
        /// Response to a *INF datastring
        /// </summary>
        /// <param name="parameter">String with user specific data</param>
        void DoINF(String SID, string parameter)
        {
            //INF <CID> <field1> <field2> … <fieldN>
            // Set username and add existing values if they exist
            // Send to event
            bool newuser = false;
            bool isme = false;
            String SendBINF = "BINF " + SID;
            if (SID == client.SID)
                isme = true;

            User user = client.NickList.Get(SID);
            if (user.Username == "")
                newuser = true;

            String[] p = parameter.Split(new Char[] { ' ' });
            bool isflag = false;
            foreach (String str in p)
            {
                isflag = false;
                switch (str.Substring(0,2))
                {
                    case "I4":
                        IPAddress ip;
                        if (IPAddress.TryParse(str.Substring(2, str.Length - 2),out ip))
                            user.IP = ip;
                        //  IPv4 address without port. A zero address (0.0.0.0) means that the server should replace it with the real IP of the client.
                        break;
                    case "I6":
                        //  IPv6 address without port. A zero address ([0:0:0:0:0:0:0:0]) means that the server should replace it with the real IP of the client.
                        break;
                    case "U4":
                        user.Port = Convert.ToInt32(str.Substring(2, str.Length - 2));
                        //  Client UDP port. Sending this field to the hub with a port means that this client wants to run in active mode for UDP. If this field is missing (or empty if changing modes), it means that the client should be treated as passive.
                        break;
                    case "U6":
                        //  Same as U4, but for IPv6.
                        break;
                    case "ID":
                        user.CID = str.Substring(2, str.Length - 2);
                        break;
                    case "SS":
                        //  Share size in bytes, integer.
                            Int64 shareSize;
                            if (Int64.TryParse(str.Substring(2, str.Length - 2), out shareSize))
                                user.ShareSize = shareSize;
                            shareSize = 0;
                        break;
                    case "SF":
                        //  Number of shared files, integer
                        user.SharedFiles = Convert.ToInt32(str.Substring(2, str.Length - 2));
                        break;
                    case "VE":
                        //  Client identification, version (client specific, recommended a short identifier then a float for version number). It is important that hubs don’t discriminate clients based on their VE tag but instead rely on SUP when it comes to which clients should be allowed (for example, “we only want clients that can hash”). VE is there mainly for informative reasons, and can perhaps be used to warn users that they’re using a known buggy or vulnerable client.
                        user.Tag = str.Substring(2, str.Length - 2) ?? String.Empty;
                        break;
                    case "US":
                        //  Maximum upload speed, bits/sec, integer
                        user.MaxUploadSpeed = Convert.ToInt32(str.Substring(2, str.Length - 2));
                        break;
                    case "SL":
                        //  Upload slots open, integer
                        user.OpenSlots = Convert.ToInt32(str.Substring(2, str.Length - 2));
                        break;
                    case "AS":
                        user.AutoSlot = Convert.ToInt32(str.Substring(2, str.Length - 2));
                        //  Automatic slot allocator speed limit, bytes/sec, integer. This is the recommended method of slot allocation, the client keeps opening slots as long as its total upload speed doesn’t exceed this value. SL then serves as a minimum number of slots open.
                        break;
                    case "AM":
                        user.MaxOpenSlots = Convert.ToInt32(str.Substring(2, str.Length - 2));
                        //  Maximum number of slots open in automatic slot manager mode, integer.
                        break;
                    case "EM":
                        user.Email = str.Substring(2, str.Length - 2) ?? String.Empty;
                        //  E-Mail, string.
                        break;
                    case "NI":
                        user.Username = str.Substring(2, str.Length - 2) ?? String.Empty;
                        //  Nickname, string. Hub must ensure that this is unique (case insensitive) in each hub, to avoid confusion. Valid are all displayable characters (char code > 32) apart from space, although hubs are free to limit this further as they like with an appropriate error message.
                        break;
                    case "DE":
                        user.Description = str.Substring(2, str.Length - 2) ?? String.Empty;
                        //  Description, string. Valid are all displayable characters (char code >= 32).
                        break;
                    case "HN":
                        user.HubsNormal = Convert.ToInt32(str.Substring(2, str.Length - 2));
                        //  Hubs where user is a normal user,integer.
                        break;
                    case "HR":
                        user.HubsReg = Convert.ToInt32(str.Substring(2, str.Length - 2));
                        //  Hubs where user is registered (had to supply password), integer.
                        break;
                    case "HO":
                        user.HubsOP = Convert.ToInt32(str.Substring(2, str.Length - 2));
                        //  Hubs where user is op in, integer.
                        break;
                    case "TO":
                        user.Token = str.Substring(2, str.Length - 2);
                        //  Token (used with CTM) in the c-c connection.
                        break;
                    case "CT":
                        //1=bot, 2=registered user, 4=operator, 8=super user, 16=hub owner, 32=hub (used when the hub sends an INF about itself). Multiple types are specified by adding the numbers together. 
                        int _ct = 0;
                        if (str.Length > 2)
                            _ct = Convert.ToInt32(str.Substring(2, str.Length - 2));

                        object ct = _ct;
                        User.CT Ct = ((User.CT)ct);


                        if (Ct.HasFlag(User.CT.SUser) || Ct.HasFlag(User.CT.Owner) || Ct.HasFlag(User.CT.OP))
                        {
                            user.HubsOP++;
                            if (isme)
                                SendBINF += " HO" + user.HubsOP;
                        }
                        else if (Ct.HasFlag(User.CT.User))
                        {
                            user.HubsReg++;
                            if (isme)
                                SendBINF += " HR" + user.HubsReg;
                        }
                        else if (Ct.HasFlag(User.CT.None))
                        {
                            user.HubsNormal++;
                            if (isme)
                                SendBINF += " HN" + user.HubsNormal;
                        }
                        user.HubsStatus = ((User.CT)ct); 
                        break;
                    case "OP":
                        if (str.Length > 2)
                        {
                            if (str.Substring(2, 1) == "1")
                                isflag = true;
                            user.IsOperator = isflag;
                            if (isflag)
                                DoOpList(user);
                        }
                        //  1=op
                        break;
                    case "AW":
                        if (str.Length > 2)
                        {
                            if (str.Substring(2, 1) == "1")
                                isflag = true;
                        user.IsOperator = isflag;
                        }
                        //  1=Away (Other away modes are reserved for the future)
                        break;
                    case "BO":
                        if (str.Length > 2)
                        {
                            if (str.Substring(2, 1) == "1")
                                isflag = true;
                            user.IsOperator = isflag;
                        }
                                //  1=Bot
                        break;
                    case "HI":
                        if (str.Length > 2)
                        {
                            if (str.Substring(2, 1) == "1")
                                isflag = true;
                            user.IsOperator = isflag;

                        }
                        //  1=Hidden
                        break;
                    case "SU":
                        if (str.Length > 2)
                        {
                            user.SupportList.Add(str.Substring(2, str.Length-2));
                        }
                        break;
                }
            }
            client.NickList.Set(user);

            if (isme && SendBINF.Length > 11)
            {
                client.SendRawMessage(SendBINF + "\n");
                client.DoConnected();
                client.IsConnected = true;
            }
            if (newuser && this.client.IsConnected)
            {
                client.DoJoin(user);
            }

        }


        /// <summary>
        /// Search sent from another user
        /// </summary>
        /// <param name="parameter">String with searchresult-specific data</param>
        void DoSearch(string parameter)
        {
            // %SID TO3591973964 ANtest		<- name search
            // %SID TO2408465784 TRtest		<- TTH search
            // %SID TO2968418560 ANtest TY2	<- Directory Search
            

            SearchInfo search = new SearchInfo();
            String[] _search = parameter.Split(" ".ToCharArray());
            search.User = client.NickList.Get(_search[0]).Clone();


            foreach (string _s in _search)
            {
                switch (_s.Substring(0,2))
                {
                    case "TO":
                        search.Token = _s.Substring(2, _s.Length - 2);
                        break;
                    case "TR":
                        search.IsTTH = true;
                        break;
                    case "AN":
                        search.SearchPattern += DcClient.DcDecode(_s.Substring(2, _s.Length - 2)+ " ");
                        break;
                    case "LT":
                        search.SearchSize = Convert.ToInt64(_s.Substring(2,_s.Length-2));
                        search.SizeRestriction = SearchInfo.SearchSizeRestriction.LessThen;
                        break;
                    case "GE":
                        search.SearchSize = Convert.ToInt64(_s.Substring(2,_s.Length-2));
                        search.SizeRestriction = SearchInfo.SearchSizeRestriction.GreaterThen;
                        break;
                    case "EQ":
                        search.SearchSize = Convert.ToInt64(_s.Substring(2,_s.Length-2));
                        search.SizeRestriction = SearchInfo.SearchSizeRestriction.EqualTo;
                        break;
                    case "GR":
                        int _Group = Convert.ToInt32(_s.Substring(2, _s.Length - 2));                       
                        break;
                    case "TY":
                        var sdt = _s.Substring(2, _s.Length - 2);
                        search.DataType = ((SearchInfo.SearchDataType)Convert.ToInt32(sdt));
                        //search.DataType = ((SearchInfo.SearchDataType)sdt);
                        break;
                    case "EX":
                        search.SearchExtensions += _s.Substring(2, _s.Length - 2) + ", ";
                        search.DataType = SearchInfo.SearchDataType.Files;
                        break;
                }
            }
            if (search.SearchExtensions.EndsWith(", "))
                search.SearchExtensions = search.SearchExtensions.Substring(0, search.SearchExtensions.Length - 2);

            // Passive, we get a SID
            if (parameter.StartsWith("Hub:"))
            {
                // Try to retrieve existing user entry
                if (client.NickList.Contains(search.User.SID))
                {
                    search.User = client.NickList.Get(search.User.SID).Clone();
                }
            }
            // Active, we get an ip (and port)
            else
            {
                search.IsActive = true;
            }
            
            // Send to event
            client.DoSearch(search);
        }


        /// <summary>
        /// Response to a sent search
        /// </summary>
        /// <param name="parameter">String with searchresult-specific data</param>
        void DoSCH(string parameter)
        {
            // Unescaped regexp
            // "^(?<name>[^\s]+) (?<file>.*?)\x05{0,1}(?<size>\d*?) (?<slotsfree>\d*?)/(?<slotstotal>\d*?)\x05TTH:{0,1}(?<tth>\w{0,39}) \((?<ip>[0-9\.]{7,15}):(?<port>[0-9]{1,5})\)$"

            SearchReply sr = new SearchReply();

            // Run regexp
            Regex regExSearch = new Regex("^(?<name>[^\\s]+) (?<file>.*?)\\x05{0,1}(?<size>\\d*?) (?<slotsfree>\\d*?)/(?<slotstotal>\\d*?)\\x05TTH:{0,1}(?<tth>\\w{0,39}) \\((?<ip>[0-9\\.]{7,15}):(?<port>[0-9]{1,5})\\)$");
            Match regExResults = regExSearch.Match(parameter);

            // Set user name
            sr.User.Username = regExResults.Groups["name"].Value ?? String.Empty;

            // Set Ip address
            IPAddress address = new IPAddress(0);
            IPAddress.TryParse(regExResults.Groups["ip"].Value, out address);

            // Set port
            int port;
            Int32.TryParse(regExResults.Groups["port"].Value, out port);

            // Try to retrieve existing user entry
            if (client.NickList.Contains(sr.User.Username))
            {
                sr.User = client.NickList.Get(sr.User.Username);
                sr.User.IP = address;
                sr.User.Port = port;

                sr.User = sr.User.Clone();
            }
            else
            {
                // Find users with matching IP
                var users = client.NickList.Find(address);

                if (users.Count == 1)
                {
                    // Only one user with correct IP found, check/set his port, then clone him
                    if (users[0].Port == 0)
                    {
                        users[0].Port = port;
                    }
                    sr.User = users[0].Clone();
                }
                else
                {
                    users = client.NickList.Find(address, port);

                    if (users.Count == 1)
                    {
                        // Only one user with correct IP:port found, clone him
                        sr.User = users[0].Clone();
                    }
                }
            }

            // Set free slots
            int slotsFree;
            Int32.TryParse(regExResults.Groups["slotsfree"].Value, out slotsFree);
            sr.SlotsFree = slotsFree;

            // Set total slots
            int slotsTotal;
            Int32.TryParse(regExResults.Groups["slotstotal"].Value, out slotsTotal);
            sr.SlotsTotal = slotsTotal;

            // Set filedata
            sr.FilePath = regExResults.Groups["file"].Value ?? String.Empty;
            sr.TTH = regExResults.Groups["tth"].Value ?? String.Empty;

            // Set filesize
            Int64 fileSize = 0;
            Int64.TryParse(regExResults.Groups["size"].Value, out fileSize);
            sr.FileSize = fileSize;

            // Send to event
            client.DoSearchReply(sr);
        }
    }
}
